准备环境
三台linux服务器或者linux容器,并且安装docker环境
这里我采用容器, 需docker run docker,也就是docker上运行centos容器,centos容器中再运行docker环境;
构建容器环境,参考:https://github.com/smalldok/docker-based-tools/tree/master/dind_centos
本地构建dind_centos镜像之后,分别创建如下三个容器(对应好Ip和hostname)
role | ip | hostname | desc |
---|---|---|---|
manager node | 172.17.0.2 | docker-0 | - |
work node | 172.17.0.3 | docker-1 | - |
work node | 172.17.0.4 | docker-2 | - |
主机间开放端口
在各个节点进行通信的时候,必须开放相关的防火墙策略,其中包括:
tcp port 2377为集群管理通信
tcp and udp port 7946为节点通信
udp port 4789为网络间流量
分别在各主机上执行如下命令:
1 | 开放防火墙端口 |
centos7镜像需要安装的工具
1 | ifconfig命令支持 |
创建swarm集群
初始化集群,节点之间相互通信的IP地址为172.17.0.2,默认端口2377
1 | [root@docker-0 swarm]# docker swarm init --advertise-addr 172.17.0.2 |
swarm集群模式是否激活
1 | [root@docker-0 swarm]# docker info|grep -i swarm |
默认监听两个TCP端口
1 | 2377:集群的管理端口、7946:节点之间的通信端口 |
查看网络
1 | 默认会创建一个overlay的网络ingress、一个bridge网络docker_gwbridge |
查看集群中的节点,当有多个manager节点的时候,是通过raft协议来选取主节点,也就是leader节点
1 | [root@docker-0 swarm]# docker node ls |
swarm配置文件
1 | swarm配置文件都在/var/lib/docker/swarm目录中,有相关的证书和manager的配置文件,使用的是raft协议 |
登陆docker-1, 将docker-1节点加入集群
1 | [root@docker-1 /]# docker swarm join --token SWMTKN-1-3d7ynvjvs9kqdqaut2sir2ky42uoiv54fmwef5s7odei33a031-3rcwr1styfzw8f8j3k9mvbz9c 172.17.0.2:2377 |
登陆docker-2, 将docker-2节点加入集群
1 | [root@docker-2 /]# docker swarm join --token SWMTKN-1-3d7ynvjvs9kqdqaut2sir2ky42uoiv54fmwef5s7odei33a031-3rcwr1styfzw8f8j3k9mvbz9c 172.17.0.2:2377 |
当忘记了加入集群的token,可以使用如下命令找到,然后在node节点上直接执行,就可以加入worker节点或者manager节点
1 | [root@docker-0 swarm]# docker swarm join-token worker |
查看集群节点信息
1 | [root@docker-0 swarm]# docker node ls |
节点之间的角色可以随时进行转换(使用update进行更新)
1 | [root@docker-0 swarm]# docker node update --role manager docker-1 |
运行服务
service是一组task集合,包括多个task,一个task为一个容器;
例如运行nginx服务,从而拆解为几个nginx的容器在各个节点上进行运行;
创建名称为web的服务,基于nginx镜像
1 | [root@docker-0 swarm]# docker service create --name web nginx |
创建名称为frontweb的服务,基于nginx镜像,模式为global
1 | [root@docker-0 swarm]# docker service create --name frontweb --mode global nginx |
查看运行的服务
1 | [root@docker-0 swarm]# docker service ls |
查看web运行信息,默认情况下manager节点也可以运行容器
1 | [root@docker-0 swarm]# docker service ps web |
查看frontweb运行信息,默认情况下manager节点也可以运行容器
1 | [root@docker-0 swarm]# docker service ps frontweb |
创建服务时,服务的几种状态
prepared 表示准备,主要是从仓库拉取镜像;
starting 启动容器,进行验证容器状态;
running 正常运行
服务模式
replicated 表示副本,默认使用的mode,并且默认情况只会创建一个副本,主要使用目的是为了高可用;
global 必须在每个节点上运行一个task(也就是容器),可以看到上面创建frontweb服务时,使用global创建了三个容器;
查看frontweb服务详细信息
1 | [root@docker-0 swarm]# docker service inspect frontweb --pretty |
服务扩容缩容
集群必然涉及服务高可用,从而会有服务的扩容缩容
将web服务扩容为3个task
1 | [root@docker-0 swarm]# docker service scale web=3 |
查看服务,可看到web服务的replicas副本数量为3个
1 | [root@docker-0 swarm]# docker service ls |
可以看到三个节点上都运行了一个web服务(task)
1 | [root@docker-0 swarm]# docker service ps web |
在默认情况下,管理节点也可以运行task,所以上面可以看到Manager节点上也运行了web服务
将web服务缩容为2个
1 | [root@docker-0 swarm]# docker service scale web=2 |
查看运行的web容器
1 | [root@docker-0 swarm]# docker service ps web |
当要让swarm的manager节点不运行容器的时候,只要更改节点的状态,从Active变成Drain即可;
如果在manager上运行容器,那么当manager宕机的时候,如果不是多节点的manager,会导致服务无法进行调度;
将manager节点的状态修改为Drain状态,使得此节点不会运行相关的task任务
1 | [root@docker-0 swarm]# docker node update --availability drain docker-0 |
本来运行在docker-0节点上的所有task会被关闭,然后mode=replicated的服务会自动迁移到其他的worker节点上(这一点有利也有弊),mode=global的服务不会自动迁移
1 | [root@docker-0 swarm]# docker service ps web |
自动故障转移
当运行在某节点上的task被关闭、或者节点宕机、或者网络抖动,mode=replicated的服务会自动迁移到其他的worker节点上(这一点有利也有弊),mode=global的服务不会自动迁移;
mode=replicated时的自动迁移,在生产环境中需要考虑的一个问题就是,如果出现了自动failover,那么其他的机器是否有足够的资源来创建这些容器,所以需要考虑node节点剩余资源的问题,例如cpu和内存;
场景模拟:
1、mode=replicated的web服务,在docker-1和docker-2这2个节点上分别运行着一个task;
2、模拟将docker-2节点进行宕机(关闭docker服务),观察docker-1节点上将会有2个task运行;
3、恢复docker-2节点服务,观察docker-1节点上还是会有2个task运行,docker-2节点不会有task运行,可以看到并不会把之前迁移到docker-1上的服务自动迁移回来,所以这里也比较坑;
模拟宕机前:
服务名 | 节点主机名 | 运行状态 |
---|---|---|
web.1 | docker-1 | Running |
web.2 | docker-2 | Running |
模拟宕机后(docker-2宕机):
服务名 | 节点主机名 | 运行状态 |
---|---|---|
web.1 | docker-1 | Running |
web.2 | docker-1 | Running |
web.2 | docker-2 | Shutdown |
模拟恢复后:
服务名 | 节点主机名 | 运行状态 |
---|---|---|
web.1 | docker-1 | Running |
web.2 | docker-1 | Running |
web.2 | docker-2 | Shutdown |
查看服务分布
1 | [root@docker-0 swarm]# docker service ps web |
模拟docker-2宕机
1 | 登陆docker-2节点,关闭docker服务 |
查看节点信息,可以看到docker-2已经为down,也就是主机宕机
1 | [root@docker-0 swarm]# docker node ls |
观察docker-1节点上将会有2个task运行,宕机的docker-2标记为shutdown
1 | [root@docker-0 swarm]# docker service ps web |
将docker-2节点进行上线
1 | [root@docker-2 /]# systemctl start docker |
查看服务节点信息,docker-2状态变为ready,表示可以运行task任务
1 | [root@docker-0 swarm]# docker node ls |
再次查看web服务的分布,服务没有再迁移回docker-2节点
1 | [root@docker-0 swarm]# docker service ps web |
访问服务
访问服务分为两种,对内、对外;
对内:内部访问的服务,不对外开放端口
对外:外部节点可访问,对外开放端口,也就是主机映射端口
创建2个副本的httpd服务
1 | [root@docker-0 swarm]# docker service create --name web --replicas=2 httpd |
或者
1 | [root@docker-0 swarm]# docker service create --name web --publish 8000:80 --replicas=2 httpd |
查看运行容器的主机
1 | [root@docker-0 swarm]# docker service ps web |
登陆节点服务,查看运行的容器
1 | [root@docker-1 /]# docker ps |
查看运行容器的IP地址
1 | [root@docker-1 /]# docker exec 74429db851d3 ip addr show |
添加主机映射端口,将服务对外开放,使外部能访问此服务
1 | [root@docker-0 swarm]# docker service update --publish-add 8000:80 web |
以上使用的就是routing mesh功能,在swarm内部实现了负载均衡,使用的网络为swarm自动创建的overlay网络。
当使用publish端口时,最大坏处是对外暴露了端口号,而且在使用的时候,如果进行了故障转移,那么多个服务运行在同一个主机上面,会造成端口占用冲突;
服务发现
docker-swarm原生提供了服务发现功能,也可以使用外部中间件,如consul/etcd/zookeeper;
创建overlay网络
要使用服务发现,需要相互通信的service必须属于同一个overlay网络,所以我们先创建一个新的overlay网络;
1 | [root@docker-0 swarm]# docker network create --driver overlay web |
默认的ingress没有提供服务发现,必须创建自己的overlay网络;
部署service到新建的overlay
部署一个web服务,并将其挂载到新创建的overlay网络
1 | [root@docker-0 swarm]# docker service create --name my-web --replicas=2 --network web httpd |
部署一个test服务用于测试,挂载到同一个overlay网络
1 | [root@docker-0 swarm]# docker service create --name test --network web busybox sleep 10000000 |
验证:
通过docker service ps test确认test所在节点在docker-1;
登陆到docker-1节点,并进入test容器,然后ping my-web
1 | [root@docker-1 /]# docker ps |
10.0.0.5是虚拟IP
查看各个主机的IP地址
1 | [root@docker-1 /]# docker exec test.1.z6ozex5qo74781icswy4245pd nslookup tasks.my-web |
查看VIP地址
1 | [root@docker-1 /]# docker exec test.1.z6ozex5qo74781icswy4245pd nslookup my-web |
对于服务使用者,test服务根本就不需要知道my-web副本的IP、VIP,只需直接用service的名字就能访问服务;
滚动更新service
当需要进行更新的时候,swarm可以设定相应的策略来进行更新,例如并行更新多少个容器,更新时间间隔等;
滚动更新降低了应用更新的风险,如果某个副本更新失败,整个更新将暂停,其他副本则可以继续提供服务,同时,在更新的过程中,总是有副本在运行的,因此也保证了业务连续性;
swarm安装如下步骤进行滚动更新:
- 停止第一个副本
- 调度任务。选择worker node
- 在worker上用新的镜像启动副本
- 如果更新成功则继续更新下一个,如果失败,暂停整个更新的过程
测试:创建2个副本的service,镜像使用httpd:2.2.31,然后将其更新到httpd:2.2.32
1 | [root@docker-0 swarm]# docker service create --name web-update --replicas=2 httpd:2.2.31 |
更新前,先设置更新的并发数和更新的时间间隔,--update-parallelism设置并行更新的副本数,通过--update-delay指定滚动更新的时间间隔
1 | 副本增加到6个,并且设置更新并发数为2个,时间间隔为1分5秒 |
将目前的6个副本从httpd:2.2.32, 更新到httpd:2.4.16
1 | 可以观察到,每次2个副本同时更新,1分05秒后进行下一组的2个副本更新 |
如果在中途更新失败,则暂停整个集群的更新;
那么将会导致整个集群的副本版本不一致,这时需要rollback回滚;
更新回滚
swarm有个方便的回滚功能,如果更新后的效果不理想,可以通过–rollback快速恢复到更新之前的状态;
但是它只能回滚到上一次执行docker service update之前的状态,并不能无限制回滚;
1 | [root@docker-0 swarm]# docker service update --rollback web-update |
测试下来,这个回滚并不是很稳定,会有回滚失败导致容器副本数减少、有些副本回滚没成功问题;
节点标签
给node节点加label标签,用于控制task运行在哪些节点;
生产环境上有时需要手动将容器分布在不同的节点;
更新节点属性,为节点添加标签
1 | [root@docker-0 swarm]# docker node update --label-add ncname=docker-1-label docker-1 |
查看设置的标签
1 | [root@docker-0 swarm]# docker node inspect docker-1 --pretty |
根据label , 指定节点来运行相关的任务
1 | [root@docker-0 swarm]# docker service create --name web --constraint node.labels.ncname==docker-1-label --replicas 2 nginx |
去掉指定的标签
1 | [root@docker-0 swarm]# docker service update --constraint-rm node.labels.ncname==docker-1-label web |
服务迁移到另一台主机上
1 | [root@docker-0 swarm]# docker service update --constraint-add node.labels.ncname==docker-2-label web |
这种修改也可以使用rollback进行回滚;
但我使用回滚命令,然而并没有什么卵用,留待以后再做验证1
[root@docker-0 swarm]# docker service update --rollback web
健康检查
在企业生产环境中,合理的健康检查设置可以保证应用的可用性。现在很多应用框架已经内置了监控检查能力,比如Spring Boot Actuator。配合Docker内置的健康检测机制,可以非常简洁实现应用可用性监控,自动故障处理,和零宕机更新。
有两种方式:
- dockerfile 中加指令方式
- docker service create 参数指令方式
dockerfile 中加指令方式
HEALTHCHECK 指令格式:
- HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令
- HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉
注:在Dockerfile中 HEALTHCHECK 只可以出现一次,如果写了多个,只有最后一个生效。
使用包含 HEALTHCHECK 指令的dockerfile构建出来的镜像,在实例化Docker容器的时候,就具备了健康状态检查的功能。启动容器后会自动进行健康检查。
HEALTHCHECK 支持下列选项:
- –interval=<间隔>:两次健康检查的间隔,默认为 30 秒;
- –timeout=<间隔>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
- –retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。
- –start-period=<间隔>: 应用的启动的初始化时间,在启动过程中的健康检查失效不会计入,默认 0 秒; (从17.05)引入
在 HEALTHCHECK [选项] CMD 后面的命令,格式和 ENTRYPOINT 一样,分为 shell 格式,和 exec 格式。命令的返回值决定了该次健康检查的成功与否:
- 0:成功;
- 1:失败;
- 2:保留值,不要使用
容器启动之后,初始状态会为 starting (启动中)。Docker Engine会等待 interval 时间,开始执行健康检查命令,并周期性执行。如果单次检查返回值非0或者运行需要比指定 timeout 时间还长,则本次检查被认为失败。如果健康检查连续失败超过了 retries 重试次数,状态就会变为 unhealthy (不健康)。
注:
- 一旦有一次健康检查成功,Docker会将容器置回 healthy (健康)状态
- 当容器的健康状态发生变化时,Docker Engine会发出一个 health_status 事件。
假设我们有个镜像是个最简单的 Web 服务,我们希望增加健康检查来判断其 Web 服务是否在正常工作,我们可以用 curl来帮助判断,其 Dockerfile 的 HEALTHCHECK 可以这么写:1
2
3
4FROM elasticsearch:5.5
HEALTHCHECK --interval=5s --timeout=2s --retries=12 \
CMD curl --silent --fail localhost:9200/_cluster/health || exit 1
1 | [root@docker-1 es]# ll |
查看Elasticsearch容器从 starting 状态进入了 healthy 状态
1 | [root@docker-1 es]# docker ps |
另外一种方法是在 docker run 命令中,直接指明healthcheck相关策略。
1 | docker run --rm -d \ |
为了帮助排障,健康检查命令的输出(包括 stdout 以及 stderr)都会被存储于健康状态里,可以用 docker inspect 来查看。我们可以通过如下命令,来获取过去5个容器的健康检查结果
1 | docker inspect --format='{{json .State.Health}}' elasticsearch |
返回1
2
3
4
5
6
7
8
9
10
11
12{
"Status": "healthy",
"FailingStreak": 0,
"Log": [
{
"Start": "2018-06-28T09:04:45.612342812Z",
"End": "2018-06-28T09:04:45.736592584Z",
"ExitCode": 0,
"Output": "..."
},
...
}
建议在Dockerfile中声明相应的健康检查策略,这样可以方便镜像的使用
Docker社区为提供了一些包含健康检查的实例镜像,我们可以在如下项目中获取 https://github.com/docker-library/healthcheck
docker service create 参数指令方式
在Docker 1.13之后,在Docker Swarm mode中提供了对健康检查策略的支持
可以在 docker service create 命令中指明健康检查策略
1 | docker service create -d \ |
在Swarm模式下,Swarm manager会监控服务task的健康状态,如果容器进入 unhealthy 状态,它会停止容器并且重新启动一个新容器来取代它。这个过程中会自动更新服务的 load balancer (routing mesh) 后端或者 DNS记录,可以保障服务的可用性。
在1.13版本之后,在服务更新阶段也增加了对健康检查的支持,这样在新容器完全启动成功并进入健康状态之前,load balancer/DNS解析不会将请求发送给它。这样可以保证应用在更新过程中请求不会中断。
常见问题
空间不足
如果哪天发现镜像无法构建,那一般都是空间问题;
方法一:暴力清除1
2
3
4
5
6
7
8 查看哪个目录占用空间最大
du -sh /*
停止docker服务
systemctl stop docker
删除/var/lib/docker目录下的所有文件
rm -rf /var/lib/docker
启动docker服务
systemctl start docker
注意:docker 服务重启后,需要重新加入集群节点1
docker swarm join --token SWMTKN-1-3d7ynvjvs9kqdqaut2sir2ky42uoiv54fmwef5s7odei33a031-3rcwr1styfzw8f8j3k9mvbz9c 172.17.0.2:2377
方法二: 转移/var/lib/docker目录
略…